<?php

require_once "Zend/Tokenizer/String/Interface.php";

/**
 * @category   Zend
 * @package    Zend_Tokenizer
 * @copyright  Salomon BRYS
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 *
 * This tokenizer gives tokens representing a line from the stream.
 */
abstract class Zend_Tokenizer_Serialized_Abstract implements Zend_Tokenizer_Interface
{
	/**
	 * @var Zend_Tokenizer_Interface
	 */
	protected $_producer;

	/**
	 * @var string
	 */
	protected $_data;

	/**
	 * @var array
	 */
	protected $_tokens = array();

	/**
	 * @var bool
	 */
	protected $_reSerializeToGetLen = true;

	/**
	 * @var int
	 */
	protected $_pos = 0;

	/**
	 * Constructor
	 *
	 * @param Zend_Tokenizer_Interface $producer
	 */
	public function __construct(Zend_Tokenizer_Interface $producer, $reSerializeToGetLen = true)
	{
		if (!$producer->isStringToken()) {
			require_once 'Zend/Exception.php';
			throw new Zend_Exception("Zend_Tokenizer_Serialized_* needs a string token producer.");
		}
		$this->_producer = $producer;
		$this->_reSerializeToGetLen = $reSerializeToGetLen;
	}

	/**
	 * Add the buffered data not tokenized yet.
	 * Used to transfer data when tokenizer is changing on the fly.
	 *
	 * @return string
	 */
	public function getRawData()
	{
		return $this->_data . $this->_producer->getRawData();
	}

	/**
	 * return wether there's at least one token ready.
	 *
	 * @return bool
	 */
	public function hasToken()
	{
		$this->_fillDataAndToken();
		return !empty($this->_tokens);
	}

	/**
	 * Return the next token.
	 *
	 * @return mixed
	 */
	public function getToken()
	{
		$this->_fillDataAndToken();
		if (empty($this->_tokens))
			return null;
		return array_shift($this->_tokens);
	}

	/**
	 * Fill the _data propriety with producer's string tokens.
	 */
	protected function _fillDataAndToken()
	{
		while ($this->_producer->hasToken()) {
			$this->_data .= $this->_producer->getToken();
		}

		$success = true;
		while ($success && !empty($this->_data)) {
			$success = false;
			$token = null;
			if ($this->_reSerializeToGetLen) {
				if ($success = $this->unserialize($this->_data, $token)) {
					$this->_data = substr($this->_data, strlen($this->serialize($token)));
				}
			} else {
				for (; $this->_pos < strlen($this->_data); ++$this->_pos) {
					$data = substr($this->_data, 0, $this->_pos + 1);
					if ($success = $this->unserialize($data, $token)) {
						$this->_data = substr($this->_data, $this->_pos + 1);
						$this->_pos = 0;
						break ;
					}
				}
			}
			if ($success) {
				$this->_tokens[] = $token;
			}
		}
	}

	/**
	 * Return wether it is the of the stream.
	 *
	 * @return bool
	 */
	public function isEndOfStream()
	{
		return $this->_producer->isEndOfStream();
	}

	/**
	 * Get the 'first' tokenizer. The producer.
	 * If a decorator, returns the decorated tokenizer.
	 * If a producer, returns itself.
	 *
	 * @return Zend_Tokenizer_Interface
	 */
	public function getProducer()
	{
		return $this->_producer;
	}

	abstract protected function unserialize($data, &$token);
	abstract protected function serialize($obj);
}
